---
title: ValueEditor
description: Dynamic editor component
---

import { QueryBuilderEmbed } from '@site/src/components/QueryBuilderEmbed';
import { useState } from 'react';
import { ValueEditor } from 'react-querybuilder';

%importmd ../\_ts_admonition.md

`ValueEditor` renders an `<input>` by default, or a `<select>` (via [`ValueSelector`](./valueselector)) for list-based values. It uses the [`useValueEditor`](../utils/hooks#usevalueeditor) hook to process values and manage list functionality.

:::note

React Query Builder doesn't restrict rule `value` properties to JavaScript primitives but maintains query object serializability. Editors that generate non-serializable values (like date pickers) store them as strings, then parse them back to the required types during rendering. Multiple-value editors (for "between" or "in" operators) typically use comma-separated strings.

:::

## Showcase

`ValueEditor` is the most commonly [customized component](./querybuilder-controlelements#valueeditor) ([more information here](../tips/custom-with-fallback)), yet it handles many scenarios out of the box. The query builder below demonstrates all supported [`Field#valueEditorType` and `Field#inputType` properties](../typescript#fields).

Rules 10 and 11 demonstrate the behavior when the `operator` is "between". Rule 12 demonstrates the behavior when a field's `valueSources` property is set to `["field", "value"]`.

:::note

This query builder has been specially configured:

1. The rule index (plus one) in the `rules` array is displayed to the left of the field selector.
2. The value stored in the rule object is displayed to the right of each value editor.
3. To focus attention on value editor components, all other components have been disabled or hidden.

:::

export const ShowcaseFieldSelector = ({ value, options, path }) => (
  <span>
    {`${path[0] + 1}. `}
    <select value={value} disabled>
      {options.map(o => (
        <option key={o.name} value={o.name}>
          {o.valueSources?.includes('field')
            ? `valueSource: "field"`
            : 'inputType' in o
              ? `inputType: "${o.label}"`
              : `valueEditorType: "${o.label}"`}
        </option>
      ))}
    </select>
  </span>
);
export const ShowcaseOperatorSelector = ({ value, options }) => (
  <select value={value} disabled>
    {options.map(o => (
      <option key={o.name} value={o.name}>
        {o.label}
      </option>
    ))}
  </select>
);
export const ShowcaseValueEditor = props => (
  <div style={{ display: 'flex' }}>
    <ValueEditor {...props} skipHook />
    {'\u00a0'}
    <span
      style={{
        fontSize: 'small',
        opacity: 0.5,
      }}>{`(stored value: ${
      typeof props.value === 'boolean' ? props.value : JSON.stringify(props.value)
    })`}</span>
  </div>
);
export const values = [
  { name: 'option1', label: 'Option 1' },
  { name: 'option2', label: 'Option 2' },
  { name: 'option3', label: 'Option 3' },
  { name: 'option4', label: 'Option 4' },
];
export const fields = [
  { name: 'text', label: 'text', inputType: 'text' },
  {
    name: 'select',
    label: 'select',
    valueEditorType: 'select',
    values,
  },
  { name: 'checkbox', label: 'checkbox', valueEditorType: 'checkbox' },
  {
    name: 'radio',
    label: 'radio',
    valueEditorType: 'radio',
    values,
  },
  { name: 'textarea', label: 'textarea', valueEditorType: 'textarea' },
  {
    name: 'multiselect',
    label: 'multiselect',
    valueEditorType: 'multiselect',
    values,
  },
  { name: 'date', label: 'date', inputType: 'date' },
  { name: 'datetime-local', label: 'datetime-local', inputType: 'datetime-local' },
  { name: 'time', label: 'time', inputType: 'time' },
  { name: 'field', label: 'field', valueSources: ['field', 'value'] },
];
export const operators = [
  { name: '=', label: '=' },
  { name: 'in', label: 'in' },
  { name: 'between', label: 'between' },
];
export const defaultQuery = {
  combinator: 'and',
  rules: [
    { field: 'text', operator: '=', value: '' },
    { field: 'select', operator: '=', value: 'option2' },
    { field: 'checkbox', operator: '=', value: true },
    { field: 'radio', operator: '=', value: 'option2' },
    { field: 'textarea', operator: '=', value: '' },
    { field: 'multiselect', operator: 'in', value: 'option1,option2' },
    { field: 'date', operator: '=', value: '' },
    { field: 'datetime-local', operator: '=', value: '' },
    { field: 'time', operator: '=', value: '' },
    { field: 'text', operator: 'between', value: 'A,Z' },
    { field: 'select', operator: 'between', value: 'option2,option4' },
    { field: 'field', operator: '=', value: 'text', valueSource: 'field' },
  ],
};
export const NullComponent = () => null;

export const Showcase = () => {
  const [query, setQuery] = useState(defaultQuery);
  const [listsAsArrays, setListsAsArrays] = useState(false);
  return (
    <>
      <p>
        <label>
          <input
            type="checkbox"
            checked={listsAsArrays}
            onChange={e => {
              setListsAsArrays(e.target.checked);
            }}
          />
          <code>listsAsArrays</code>
        </label>
      </p>
      <QueryBuilderEmbed
        fields={fields}
        query={query}
        onQueryChange={setQuery}
        listsAsArrays={listsAsArrays}
        operators={operators}
        controlElements={{
          fieldSelector: ShowcaseFieldSelector,
          operatorSelector: ShowcaseOperatorSelector,
          combinatorSelector: NullComponent,
          addRuleAction: NullComponent,
          addGroupAction: NullComponent,
          removeRuleAction: NullComponent,
          removeGroupAction: NullComponent,
          valueEditor: ShowcaseValueEditor,
        }}
      />
    </>
  );
};

<Showcase />

:::tip

The [compatibility packages](../compat) also implement these options using appropriate components from their respective style libraries ([see the demo](/demo)).

:::

## Value sources

By default, React Query Builder facilitates comparing fields (typically database column names) to _values_ (strings, numbers, etc.). However, some situations require comparisons between two fields. Consider this SQL statement:

```sql
SELECT *
  FROM users
 WHERE lastName LIKE firstName || '%';
```

This returns records where the user's last name begins with the same letters as their first name and ends with any number of other characters (including zero). "Kris Kristofferson" would meet this criteria.

To represent that `WHERE` clause in React Query Builder, use the [`getValueSources` prop](./querybuilder#getvaluesources) or the `valueSources` property of the `lastName` field in the `fields` prop. When either evaluates to an array containing "field" and the rule's `valueSource` property is "field", the `<ValueEditor />` component displays a dropdown list of fields instead of values.

### Example

In the query builder above, the definition for the field used in the last rule is:

```json
{
  "name": "field",
  "label": "field",
  "valueSources": ["field", "value"]
}
```

The `defaultQuery` prop looks like this:

```json
{
  "combinator": "and",
  "rules": [
    // ...other rules...
    { "field": "field", "operator": "=", "value": "text", "valueSource": "field" }
  ]
}
```

Since the field's `valueSources` property is an array with more than one element, a ["value source selector" component](./querybuilder-controlelements#valuesourceselector) appears to the left of the `<ValueEditor />`.

:::tip

To reorder rule components (e.g., move the value source selector to the right of the value editor), use the CSS Flexbox `order` property.

:::

:::note

The `Field#valueSources` property can also be a function that takes an operator name as its parameter and returns the required array.

:::

### Filtering the field list

Not all fields are compatible with each other, so each field may provide a `comparator` property specifying which fields appear in the value editor list. When `comparator` is a string, only fields with the same value as the current field in the property matching the `comparator` string are listed.

Here's an example. In the following field list, fields `f1`, `f2`, and `f3` share a common property `datatype: "number"`. `f1` identifies the `datatype` property as its `comparator`. When a rule specifies field `f1`, only `f2` and `f3` appear in the dropdown list because their `datatype` property matches `f1`'s. `f4` and `f5` are excluded because their `datatype` property doesn't match `f1`'s, and `f1` is excluded because a rule's selected field cannot be compared with itself.

```tsx
const fields: Field[] = {
  {
    name: 'f1',
    label: 'f1',
    // highlight-start
    valueSources: ["field"],
    comparator: 'datatype',
    datatype: 'number'
    // highlight-end
  },
  { name: 'f2', label: 'f2', datatype: 'number' },
  { name: 'f3', label: 'f3', datatype: 'number' },
  { name: 'f4', label: 'f4', datatype: 'string' },
  { name: 'f5', label: 'f5', datatype: 'string' },
}
```

The `comparator` property can also be a function that takes a `Field` object as its parameter. Each field in the `fields` prop is evaluated by this function, which should return `true` to include the field in the list or `false` to exclude it. The equivalent configuration using a function for `comparator`:

```tsx
const fields: Field[] = {
  { name: 'f1',
    label: 'f1',
    // highlight-start
    valueSources: ["field"],
    comparator: f => f.datatype === 'number'
    // highlight-end
  },
  { name: 'f2', label: 'f2', datatype: 'number' },
  { name: 'f3', label: 'f3', datatype: 'number' },
  { name: 'f4', label: 'f4', datatype: 'string' },
  { name: 'f5', label: 'f5', datatype: 'string' },
}
```
